﻿using System;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.ValueProviders;
using MeetSomeNearbyStranger.DataModels;
using MeetSomeNearbyStrangerWebApi.Attributes;
using MeetSomeNearbyStrangerWebApi.Mappers;
using MeetSomeNearbyStrangerWebApi.Models;
using MeetSomeNearbyStrangerWebApi.Repositories;
using MeetSomeNearbyStrangerWebApi.Validators;
using System.Collections.Generic;
using System.Drawing;
using MeetSomeNearbyStrangerWebApi.ConfigurationSettings;
using MeetSomeNearbyStrangerWebApi.SSL;

namespace MeetSomeNearbyStrangerWebApi.Controllers
{
    public class UsersController : BaseController
    {
        private DbUsersRepository usersRepository;
        private DropboxUploader uploader;
        private DbMeetsRepository meetsRepository;
        private PubNubSender pubnub;

        public UsersController(DbUsersRepository repository, DropboxUploader uploader, DbMeetsRepository meetsRepository, PubNubSender pubnub)
        {
            this.usersRepository = repository;
            this.uploader = uploader;
            this.meetsRepository = meetsRepository;
            this.pubnub = pubnub;
        }

        [RequireHttps]
        [HttpPost, ActionName(UsersControllerActions.SendUserData)]
        public HttpResponseMessage RegisterUser(UserRegisterModel userModel)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
                () =>
                {
                    UserValidator.ValidateNewUserData(userModel);

                    User duplicatedUser = this.usersRepository.GetByNickname(userModel.Nickname);
                    if (duplicatedUser != null)
                    {
                        return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Nickname is already taken.");
                    }
                    duplicatedUser = this.usersRepository.GetByUserID(userModel.UserID);
                    if (duplicatedUser != null)
                    {
                        return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "An user has already been associated with this device.");
                    }

                    User newUser = null;
                    try
                    {
                        newUser = UsersMapper.ToUserEntity(userModel);
                    }
                    catch (Exception)
                    {
                        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid user register model provided!");
                    }

                    newUser.LastSeen = DateTime.Now;
                    usersRepository.Add(newUser);

                    var response = this.Request.CreateResponse(HttpStatusCode.NoContent);
                    return response;
                }
            );

            return responseMsg;
        }

        [HttpPost, ActionName(UsersControllerActions.SetGCMRegID)]
        public HttpResponseMessage SetGcmRegID(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, string regId)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   user.GCMRegID = regId;
                   this.usersRepository.Update(user.Id, user);

                   HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
                   return response;
               });

            return responseMsg;
        }

        [HttpGet, ActionName(UsersControllerActions.GetMyProfile)]
        public HttpResponseMessage GetMyProfile(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   string imageLocation = null;
                   if (user.ProfileImage != null)
                   {
                       imageLocation = this.uploader.RenewImageLocation(user.ProfileImage.ImageFolder, user.ProfileImage.ImageName);
                   }

                   ProfileModel profile = UsersMapper.ToProfileModel(user, imageLocation);

                   HttpResponseMessage response = this.Request.CreateResponse<ProfileModel>(HttpStatusCode.OK, profile);
                   return response;
               });

            return responseMsg;
        }

        [HttpGet, ActionName(UsersControllerActions.GetProfile)]
        public HttpResponseMessage GetProfile(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, string nickname)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Device is not registered in the system!");
                   }

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   User inspectedUser = this.usersRepository.GetByNickname(nickname);

                   string imageLocation = null;
                   if (inspectedUser.ProfileImage != null)
                   {
                       imageLocation = this.uploader.RenewImageLocation(inspectedUser.ProfileImage.ImageFolder, inspectedUser.ProfileImage.ImageName);
                   }

                   string status = "";
                   Meet meet = this.meetsRepository.GetAll().SingleOrDefault(x => (x.Initiator.Nickname == user.Nickname && x.Target.Nickname == inspectedUser.Nickname) ||
                       (x.Initiator.Nickname == inspectedUser.Nickname && x.Target.Nickname == user.Nickname));
                   if (meet != null)
                   {
                       if (meet.Initiator.Nickname == user.Nickname)
                       {
                           if (meet.Accepted)
                           {
                               status = "Meat request accepted";
                           }
                           else if (meet.Declined)
                           {
                               status = "Meat request declined";
                           }
                           else
                           {
                               status = "Meat request send";
                           }
                       }
                       else
                       {
                           status = "Meat request received";
                       }
                   }
                   ProfileModel profile = UsersMapper.ToProfileModel(inspectedUser, imageLocation, status);

                   HttpResponseMessage response = this.Request.CreateResponse<ProfileModel>(HttpStatusCode.OK, profile);
                   return response;
               });

            return responseMsg;
        }

        [HttpPost, ActionName(UsersControllerActions.UploadImage)]
        public HttpResponseMessage UploadImage(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, [FromBody]EncodedImage encImage)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "User is not registered in the system!");
                   }

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   byte[] imageBytes = Convert.FromBase64String(encImage.Base64String);

                   MemoryStream ms = new MemoryStream(imageBytes, 0, imageBytes.Length);
                   ms.Write(imageBytes, 0, imageBytes.Length);
                   Image image = Image.FromStream(ms, true);
                   
                   string filePath = Path.GetTempPath() + Guid.NewGuid().ToString() + ".jpg";
                   image.Save(filePath, ImageFormat.Jpeg);

                   ms.Dispose();
                   ms.Close();

                   ProfileImage profileImage = this.uploader.UploadProfileImage(filePath);

                   user.ProfileImage = profileImage;
                   this.usersRepository.Update(user.Id, user);

                   File.Delete(filePath);

                   HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.Created, profileImage.ImagePublicLocation);
                   return response;
               });

            return responseMsg;
        }

        [HttpGet, ActionName(UsersControllerActions.GetMyInterests)]
        public HttpResponseMessage GetMyInterests(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   IEnumerable<Interest> interests = user.Interests;

                   HttpResponseMessage response = this.Request.CreateResponse<IEnumerable<Interest>>(HttpStatusCode.OK, interests);
                   return response;
               });

            return responseMsg;
        }

        [HttpGet, ActionName(UsersControllerActions.GetOtherInterests)]
        public HttpResponseMessage GetOtherInterests(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, string nickname)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   User otherUser = null;
                   try
                   {
                       otherUser = this.usersRepository.GetByNickname(nickname);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Inspected user is not registered in the system!");
                   }

                   IEnumerable<Interest> interests = otherUser.Interests;

                   HttpResponseMessage response = this.Request.CreateResponse<IEnumerable<Interest>>(HttpStatusCode.OK, interests);
                   return response;
               });

            return responseMsg;
        }

        [HttpPut, ActionName(UsersControllerActions.SendLocation)]
        public HttpResponseMessage SendLocation(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, double latitude, double longitude)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Device is not registered in the system!");
                   }

                   user.Latitude = latitude;
                   user.Longitude = longitude;
                   this.usersRepository.Update(user.Id, user);

                   HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
                   return response;
               });

            return responseMsg;
        }

        [HttpPut, ActionName(UsersControllerActions.AddInterest)]
        public HttpResponseMessage AddInterest(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, string interestKey)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   if (user.Interests == null)
                   {
                       user.Interests = new HashSet<Interest>();
                   }
                   user.Interests.Add(new Interest()
                   {
                       Key = interestKey
                   });
                   this.usersRepository.Update(user.Id, user);

                   HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK, user.Interests.Count);
                   return response;
               });

            return responseMsg;
        }

        [HttpGet, ActionName(UsersControllerActions.GetUsersMediumView)]
        public HttpResponseMessage GetUsersMediumView(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   IEnumerable<User> userEntities = this.usersRepository.GetAll().AsEnumerable();
                   try
                   {
                       foreach (var entity in userEntities)
                       {
                           var pic = entity.ProfileImage;
                           if (pic != null)
                           {
                               pic.ImagePublicLocation = this.uploader.RenewImageLocation(pic.ImageFolder, pic.ImageName);
                           }
                       }
                   }
                   catch (Exception)
                   {
                   }

                   IEnumerable<UserMediumModel> users =
                       from userEntity in userEntities
                       where userEntity.Nickname != user.Nickname
                       select UsersMapper.ToUserMediumModel(userEntity);

                   HttpResponseMessage response = this.Request.CreateResponse<IEnumerable<UserMediumModel>>(HttpStatusCode.OK, users);
                   return response;
               });

            return responseMsg;
        }

        [HttpGet, ActionName(UsersControllerActions.GetUsersOnVisibleMap)]
        public HttpResponseMessage GetUsersShortView(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))]string userID, 
            double bottomLeftX, double bottomLeftY, double upperRightX, double upperRightY, double timeInSec)
        {
            HttpResponseMessage responseMsg = this.PerformOperationAndHandleExceptions(
               () =>
               {
                   User user = null;
                   try
                   {
                       user = this.usersRepository.GetByUserID(userID);
                   }
                   catch (Exception)
                   {
                       return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User is not registered in the system!");
                   }

                   //added
                   Area area = new Area()
                   {
                       BottomLeftCorner = new Corner()
                       {
                           X = bottomLeftX,
                           Y = bottomLeftY
                       },
                       UpperRightCorner = new Corner()
                       {
                           X = upperRightX,
                           Y = upperRightY
                       }
                   };

                   user.LastSeen = DateTime.Now;
                   this.usersRepository.Update(user.Id, user);

                   IEnumerable<User> userEntities = this.usersRepository.GetAll().AsEnumerable();
                   foreach (var entity in userEntities)
                   {
                       var pic = entity.ProfileImage;
                       if (pic != null)
                       {
                           pic.ImagePublicLocation = this.uploader.RenewImageLocation(pic.ImageFolder, pic.ImageName);
                       }
                   }

                   IEnumerable<UserShortModel> users =
                       from userEntity in userEntities
                       where userEntity.Nickname != user.Nickname && 
                             IsWithin(area, new Models.Point() { X = userEntity.Longitude, Y = userEntity.Latitude }) &&
                             (DateTime.Now - userEntity.LastSeen).TotalSeconds < timeInSec
                       select UsersMapper.ToUserShortModel(userEntity);

                   HttpResponseMessage response = this.Request.CreateResponse<IEnumerable<UserShortModel>>(HttpStatusCode.OK, users);
                   return response;
               });

            return responseMsg;
        }

        [NonAction]
        private static bool IsWithin(Area area, Models.Point point)
        {
            if (area.BottomLeftCorner.X <= point.X && point.X <= area.UpperRightCorner.X &&
                area.BottomLeftCorner.Y <= point.Y && point.Y <= area.UpperRightCorner.Y)
            {
                return true;
            }
            return false;
        }
    }
}
